package org.jsequences;

import java.util.AbstractList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.RandomAccess;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;

/**
 * Sequences provides the core operations to instantiate, join and process Iterables. The class is
 * designed to be used via static imports can be of use nearly everywhere where an efficient
 * processing of iterables is needed. Sequences is not a kitchen sink and contains only a very small
 * amount of methods - with many overloads:
 * <ol>
 * <li><code><b>from</b></code>: Returns a fluent view of a collection or a function which can be
 * used to do functional transformations on the resulting code. An alternative to this is
 * <code>fromNullable</code> which is nullsafe. If null is encountered as the input parameter, an
 * empty fluent iterable will be returned.</li>
 * <li><code><b>seq</b></code>: Creates a sequence. Returns a <code>FluentIterable</code> out of
 * typesafe varargs. The sequence can be used for iteration as-is or it is converted into a list or
 * set with one of the toXXX-Methods.</li>
 * <li><code><b>entries</b></code>: Creates a pair-sequence of Map.Entry-Objects. This is
 * effectively a 2-dimensional sequence and is usually used as a building-block for maps. It can,
 * however, also be used for direct iteration, for example in Unit-Tests.</li>
 * <li><code><b>concat</b></code>: Concatenates two or more iterables and returns them as a
 * <code>FluentIterable</code>. This is effectively a call to {@link Iterables#concat(Iterable)}.</li>
 * </ol>
 * 
 * @author Eric Giese
 */
// CHECKSTYLE:OFF The notation style of seq with 7+ parameters blasts
// too many common checkstyle rules.
public final class Sequences {

    /**
     * Returns a lazy Iterable view of the given iterable. This view can be used
     * to apply functional transformations or filterings on the fly without
     * creating a new collection in each step.
     * 
     * @See {@link FluentIterable#from(Iterable)}
     */
    public static <E> FluentIterable<E> from(Iterable<E> iterable) {
        return FluentIterable.from(iterable);
    }

    /**
     * Returns a lazy Iterable view of the given array. This view can be used
     * to apply functional transformations or filterings on the fly without
     * creating a new collection in each step.
     * 
     * @See {@link FluentIterable#from(Iterable)}
     */
    public static <E> FluentIterable<E> from(E[] elements) {
        return from(Arrays.asList(elements));
    }

    /**
     * Returns a lazy Iterable view of the entrySet of the given map. This view can be used
     * to apply functional transformations or filterings on the fly without
     * creating a new collection in each step.
     * 
     * @See {@link FluentEntryIterable#from(Map)}
     */
    public static <K, V> FluentEntryIterable<K, V> from(Map<K, V> map) {
        return FluentEntryIterable.from(map);
    }

    /**
     * Returns a fluent function that wraps {@code function}, or {@code function} itself if it
     * is already a {@code FluentFunction}. FluentFunctions are mainly used to chain Functions with
     * other Functions, Predicates or value checks like != null.</i>
     */
    public static <F, T> FluentFunction<F, T> from(Function<F, T> function) {
        return FluentFunction.from(function);
    }

    /**
     * Returns a lazy Iterable view of the entries. This view can be used to apply functional
     * transformations or filterings on the fly without creating a new collection in each step.
     * 
     * @See {@link FluentIterable#from(Iterable)}
     */
    public static <K, V> FluentEntryIterable<K, V> fromEntries(
            Iterable<? extends Entry<K, V>> entries) {
        return FluentEntryIterable.from(entries);
    }

    /**
     * Returns the given array as a fluent iterable if it is non-null; an empty fluent iterable
     * otherwise.
     * <p>
     * <i><b>Note</b>: Use this method only if you really need to defensively check the input value
     * since you are using an API which might return null - in all other cases it is better to use
     * the fast-failing from()-method which better detects errors.</i>
     */
    public static <E> FluentIterable<E> fromNullable(E[] array) {
        return fromNullable(array != null ? Arrays.asList(array) : null);
    }

    /**
     * Returns the given iterable if it is non-null; an empty immutable fluent iterable otherwise.
     * <p>
     * <i><b>Note</b>: Use this method only if you really need to defensively check the input value
     * since you are using an API which might return null - in all other cases it is better to use
     * the fast-failing from()-method which better detects errors.</i>
     */
    public static <E> FluentIterable<E> fromNullable(Iterable<E> iterable) {
        return from(iterable != null ? iterable : ImmutableList.<E>of());
    }


    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1) {
        return construct(e1);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2) {
        return construct(e1, e2);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3) {
        return construct(e1, e2, e3);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4) {
        return construct(e1, e2, e3, e4);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5) {
        return construct(e1, e2, e3, e4, e5);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6) {
        return construct(e1, e2, e3, e4, e5, e6);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7) {
        return construct(e1, e2, e3, e4, e5, e6, e7);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9, E e10) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9, E e10, E e11) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11);
    }


    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9, E e10, E e11, E e12) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9, E e10, E e11, E e12, E e13) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9, E e10, E e11, E e12, E e13, E e14) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9, E e10, E e11, E e12, E e13, E e14, E e15) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9, E e10, E e11, E e12, E e13, E e14, E e15, E e16) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16);
    }

    /**
     * Returns an immutable <code>FluentIterable</code> containing the given nullable elements, in
     * order.
     * <p>
     * <i><b>Warning</b>: This method is used as a fallback when more than a fixed number of
     * parameters (16) should be converted to a sequence. This isn't really a problem, but thanks to
     * varargs you might encounter compiler-warnings if you use generic datatypes as the input
     * parameter, like List[String].</i>
     */
    public static <E> FluentIterable<E> seq(E e1, E e2, E e3, E e4, E e5, E e6,
            E e7, E e8, E e9, E e10, E e11, E e12, E e13, E e14, E e15, E e16, E e17, E... others) {
        return construct(ObjectArrays.concat(new Object[] {
                e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17 },
                others, Object.class));
    }

    /**
     * Returns an immutable map entry with the specified key and value. The {@link Entry#setValue}
     * operation throws an {@link UnsupportedOperationException}.
     * <p>
     * This method can be used in situations where Lists or Sets of Entries should be built manually
     * instead of using a set, and for methods which return key-value-pairs. It should be noted,
     * however, that excessive use of entries instead of 'real' datatstructures can lead to
     * unreadable code, so use it only when a key-value-pair makes semantically sense.
     * 
     * @return a Key/Value-Pair
     */
    public static <K, V> Entry<K, V> entry(K key, V value) {
        return Maps.immutableEntry(key, value);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pair, in order.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1) {
        return constructEntries(k1, v1);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2) {
        return constructEntries(k1, v1, k2, v2);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3) {
        return constructEntries(k1, v1, k2, v2, k3, v3);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8, k9, v9);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9,
            K k10, V v10) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8, k9, v9, k10, v10);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9,
            K k10, V v10, K k11, V v11) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8, k9, v9, k10, v10, k11, v11);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9,
            K k10, V v10, K k11, V v11, K k12, V v12) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8, k9, v9, k10, v10, k11, v11, k12, v12);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9,
            K k10, V v10, K k11, V v11, K k12, V v12, K k13, V v13) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8, k9, v9, k10, v10, k11, v11, k12, v12,
                k13, v13);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9,
            K k10, V v10, K k11, V v11, K k12, V v12, K k13, V v13,
            K k14, V v14) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8, k9, v9, k10, v10, k11, v11, k12, v12,
                k13, v13, k14, v14);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9,
            K k10, V v10, K k11, V v11, K k12, V v12, K k13, V v13,
            K k14, V v14, K k15, V v15) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8, k9, v9, k10, v10, k11, v11, k12, v12,
                k13, v13, k14, v14, k15, v15);
    }

    /**
     * Constructs an immutable <code>FluentEntryIterable</code> out of the given nullable
     * Key-Value-Pairs, in order. Multiple occurences of keys are possible, but will
     * cause problems when the contents are converted into a map.
     * <p>
     * <i><b>Warning</b>: This method is the last entries initializer, with a maximum key-value
     * count of 16. If you need to initialize a larger number of entries, then use the
     * concatEntries-Function in conjunction with this one:</i>
     * <p>
     * <code>concat(seq(entries(k1, v1, .... k16, v16), entries(k17, v17, ... k32, v32), entries(...)))</code>
     * <p>
     * <i> You should also consider to refactor your code. An alternative might be the use of a
     * classpath-Properties file or an enum.</i>
     */
    public static <K, V> FluentEntryIterable<K, V> entries(K k1, V v1,
            K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
            K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9,
            K k10, V v10, K k11, V v11, K k12, V v12, K k13, V v13,
            K k14, V v14, K k15, V v15, K k16, V v16) {
        return constructEntries(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                k6, v6, k7, v7, k8, v8, k9, v9, k10, v10, k11, v11, k12, v12,
                k13, v13, k14, v14, k15, v15, k16, v16);
    }


    /**
     * Combines two iterables into a single iterable. The returned iterable has an
     * iterator that traverses the elements in {@code a}, followed by the elements
     * in {@code b}. The source iterators are not polled until necessary.
     * <p>
     * Note: This method becomes unnecessary as soon as FluentIterable implements this function.
     * Sadly it currently still doesn't. :-/
     */
    public static <E> FluentIterable<E> concat(
            Iterable<? extends E> a,
            Iterable<? extends E> b) {
        return concat(ImmutableList.of(a, b));
    }

    /**
     * Combines multiple iterables into a single iterable. The returned iterable
     * has an iterator that traverses the elements of each iterable in {@code inputs}. The input
     * iterators are not polled until necessary.
     */
    public static <T> FluentIterable<T> concat(
            Iterable<? extends Iterable<? extends T>> inputs) {
        return from(Iterables.concat(inputs));
    }

    /**
     * Converts an array of any kind of objects TYPE-UNSAFE into a typed
     * Arrays.asList(). This method may only ever be used internally!
     */
    private static <K, V> FluentEntryIterable<K, V> constructEntries(Object... elements) {
        return FluentEntryIterable.from(new EntryList<K, V>(elements));
    }

    /**
     * Creates the sequence out of the internally created varargs-array.
     */
    @SuppressWarnings("unchecked")
    private static <E> FluentIterable<E> construct(Object... elements) {
        return from((List<E>) Arrays.asList(elements));
    }

    /**
     * Represents a two-dimensional List of Entries which is based on a one-dimensional Object
     * array. The cheap Entry-Objects are created out of the Array on-the-fly when an element is
     * requested by the get-Method or the iterator.
     */
    private static final class EntryList<K, V> extends AbstractList<Entry<K, V>>
            implements RandomAccess {

        private final Object[] entries;

        EntryList(Object[] entries) {
            this.entries = entries;
        }

        @Override
        @SuppressWarnings("unchecked")
        public Entry<K, V> get(int index) {
            int i = index << 1;
            return entry((K) entries[i], (V) entries[i + 1]);
        }

        @Override
        public int size() {
            return entries.length >> 1;
        }
    }

    private Sequences() {}
}
